package table;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Formatter;
import java.util.Locale;

/**
 * Converts a matrix or array to a Latex table
 * @author Anders
 *
 */
public class LatexTable {

	/*
	 * h 	where the table is declared (here)
t 	at the top of the page
b 	at the bottom of the page
p 	on a dedicated page of floats
! 	override the default float restrictions. E.g., the maximum size allowed of a b float is normally quite small; if you want a large one, you need this ! parameter as well.
	 */
	
	private Placement[] placements;	
	public enum Placement {
		Here, Top, Bottom, Dedicated, Override, None;
		
		public String toString() {
			switch(this) {
				case Here: return "h";				
				case Top: return "t";		
				case Bottom: return "b";
				case Dedicated: return "p";
				case Override: return "!";
				default: return "";
			}			
		}
	}
	
	public enum Formats {
		Decimal, Float, String;
		
		public String toString() {
			switch(this) {
				case Decimal: 	return "d";				
				case Float: 	return "f";		
				case String: 	return "s";
				default: 		return "";
			}
		}
	}
	
	public enum Alignment {
		Left, Right, Center, Width;
		
		private double width;
		private String unit = "cm";		
		
		public Alignment setWidth(double width) {
			this.width = width;
			return this;
		}
		
		public Alignment setUnit(String unit) {
			this.unit = unit;
			return this;
		}
		
		public String toString() {
			switch(this) {
				case Center: 	
					return "c";
				case Left: 		
					return "l";
				case Right: 	
					return "r";
				case Width: 	
					return "p{" + width + unit + "}";
			}
			return "";
		}		
	}
	
	public enum Borders {All, Sides, Internal, Custom}
	
	private Alignment[] alignments;
	private Borders border;
	
	private int rows, cols;
	private Object[] headers;
	
	private Object[][] table;
	private String[] units;
	private String[] format_strings;
	private Formats[] formats;
	private int decimals;
	
	private boolean isTable;
	private boolean centerTable = true;
	
	private String caption = null;
	private String label = null;

	public LatexTable(int rows, int cols, Borders border, Alignment[] alignments, boolean isTable, int decimals) {
		this.rows = rows;
		this.cols = cols;
		table = new Object[rows][cols];	
		this.border = border;
		if(alignments == null) {
			this.alignments = new Alignment[cols];
			for(int i=0; i<cols; i++)
				this.alignments[i] = Alignment.Center;
		}
		else
			this.alignments = alignments;
		
		headers = new Object[cols];
		
		units = new String[cols];
		for(int c=0; c<cols; c++)
			units[c] = "";
		
		this.decimals = decimals;
		format_strings = new String[cols];
		for(int c=0; c<cols; c++)
			format_strings[c] = "%." + decimals + "f";
		formats = new Formats[cols];
		for(int c=0; c<cols; c++)
			formats[c] = Formats.Float;
		
		placements = new Placement[8]; //ex. !h!t!b!p
		for(int i=0; i<8; i++)
			placements[i] = Placement.None;
		//default htbp
		placements[1] = Placement.Here;
//		placements[3] = Placement.Top;
//		placements[5] = Placement.Bottom;
//		placements[7] = Placement.Dedicated;		
		
		this.isTable = isTable;
	}
	
	
	public LatexTable(int rows, int cols, Borders border, Alignment[] alignments, boolean isTable) {
		this(rows, cols, border, null, isTable, 2);	
	}
	
	public LatexTable(int rows, int cols, Borders border, Alignment[] alignments) {
		this(rows, cols, border, null, false);	
	}
	
	//create table with default alignment
	public LatexTable(int rows, int cols, Borders border) {
		this(rows, cols, border, null);		
	}
	
	//create table with all borders
	public LatexTable(int rows, int cols) {			
		this(rows, cols, Borders.All);		
	}
	
	//create single row table
	public LatexTable(int rows) {
		this(rows, 1);
	}
	
	public void centerTable(boolean centerTable) {
		this.centerTable = centerTable;
	}
	
	public void setHeader(int col, Object header) {
		headers[col] = header;
	}
	
	public void addPlacement(Placement p, boolean override) {
		int idx = 0;
		switch(p) {
			case Here:
				idx = 0;
				break;
			case Top:
				idx = 1;
				break;
			case Bottom:
				idx = 2;
				break;
			case Dedicated:
				idx = 3;
				break;
			default: return;
		}
		if(override)
			placements[idx] = Placement.Override;
		placements[idx+1] = p;
	}	
	
	public void defaultPlacement() {
		
		for(int i=0; i<8; i++)
			placements[i] = Placement.None;
		placements[3] = Placement.Top;
		placements[5] = Placement.Bottom;
		placements[7] = Placement.Dedicated;
	}
	
	public void addToRowCol(int row, int col, Object O) {
		table[row][col] = O;
	}
	
	public void addToRowCol(int row, int col, double d) {
		table[row][col] = new Double(d);
	}
	
	public void addToRowCol(int row, int col, int i) {
		table[row][col] = new Integer(i);
	}
	
	public void addToRow(int row, Object[] Os) {
		table[row] = Os;
	}
	
	public void addToRow(int row, double[] A) {
		for(int c=0; c<cols; c++)
			table[row][c] = new Double(A[c]);
	}
	
	public void addToRow(int row, int[] A) {
		for(int c=0; c<cols; c++)
			table[row][c] = new Integer(A[c]);
	}
	
	public void addToCol(int col, Object[] Os) {
		for(int r=0; r<rows; r++)
			table[r][col] = Os[r];
	}
	
	public void addToCol(int col, double[] A) {
		for(int r=0; r<rows; r++)
			table[r][col] = new Double(A[r]);
	}
	
	public void addToCol(int col, int[] A) {
		for(int r=0; r<rows; r++)
			table[r][col] = new Integer(A[r]);
	}
	
	public void addToTable(Object[][] os) {
		table = os;
	}
	
	public Object getFromRowCol(int row, int col) {
		return table[row][col];
	}
	
	//TODO delete this....
	public void addToTable(int[][] is) {
		for(int r=0; r<rows; r++) {
			for(int c=0; c<cols; c++) {
				table[r][c] = new Integer(is[r][c]);
			}
		}
//		for(int c=0; c<is.length; c++)
//			this.addToCol(c, is[c]);
	}
	
	public void setUnitCol(int col, String unit) {
		units[col] = unit;
	}
	
	public void setCaption(String caption) {
		this.caption = caption;
	}
	
	public void setLabel(String label) {
		this.label = "tab:" + label;
	}
	
//	public void setFormatDecimals(int decimals) {
//		this.decimals = decimals;
//	}
	
	//TODO setFormatRow
	
	public void setFormatCol(int col, Formats format) {
		this.setFormatCol(col, format, this.decimals);
	}
	
	public void setFormatCol(int col, Formats format, int decimals) {
		this.setFormatCol(col, format, decimals, 1);
	}
	
	public void setFormatCol(int col, Formats format, int decimals, int width) {
		width = width <= 0 ? 1 : width;
		formats[col] = format;
		switch(format) {
			case Decimal: 
				format_strings[col] = "%" + width + format;
				break;
			case Float: 
				format_strings[col] = "%" + width + "." + decimals + format;
				break;
			case String: 
				format_strings[col] = "%" + width + format;
				break;
		}
		
	}
	
	public void setAlignmentCol(int col, Alignment alignment) {
		alignments[col] = alignment;
	}
	
	//************//
	//FILE WRITING//
	//************//
	
	/**
	 * write the table to disk
	 * @param filename - write table to this file
	 */
	public static BufferedWriter openForWriting(String filename) {
	    FileWriter fstream = null;
		try {
			fstream = new FileWriter(filename);
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		}
	    return new BufferedWriter(fstream);
//	    try {
//			writeToDisk(out);
//		} catch (IOException e) {
//			e.printStackTrace();
//		}
	}
	
	public void writeToDisk(BufferedWriter out) throws IOException {
				
		StringBuilder sb = new StringBuilder();
		
		sb.append("%auto-generated table by JavaToLaTeX\n");

		if(isTable) {
			sb.append("\\begin{table}[");
			for(int i=0; i<placements.length; i++)
				sb.append(placements[i].toString());			
			sb.append("]\n");
			if(centerTable) {
				sb.append("\\begin{center}\n");
			}
		}
		
		sb.append("\\begin{tabular}{");
		
		if(border == Borders.All) {
			sb.append("|");
			for(int c=0; c<cols; c++) {
				sb.append(alignments[c].toString() + "|");
			}
			sb.append("}\n\\hline");
			sb.append("\n");
		}
		
		writeHeader(sb);
		
		Formatter formatter = new Formatter(sb, Locale.ENGLISH);
//		formatter.format("%-25s %15d\n", "Task number", MediaGenerator.task_number);
		
		for(int r=0; r<rows; r++) {			
			for(int c=0; c<cols; c++) {
//				sb.append(table[r][c].toString() + " & ");	
				String end = " & ";
				if(c == cols-1)
					end = " \\\\ ";
				if(table[r][c] == null) {
					sb.append(end);
					continue;
				}
				try {
					switch(formats[c]) {
						case Decimal:							
							Double D = Double.parseDouble(table[r][c].toString());						
							formatter.format(format_strings[c] + "%s" + end,									
									D.intValue(), 
								units[c]);	
							break;
						case Float:
							formatter.format(format_strings[c] + "%s" + end, 
									Double.parseDouble(table[r][c].toString()), 
									units[c]);
							break;
						case String:
							formatter.format(format_strings[c] + "%s" + end, 
									table[r][c].toString(), 
									units[c]);
							break;
					}
					
//					formatter.format("%." + decimals + "f%s & ", Double.parseDouble(table[r][c].toString()), units[c]);
				} catch(NumberFormatException e) {
					sb.append(table[r][c].toString() + end);
				}
			}
			
//			try {
//				formatter.format(formats[cols-1] + "%s \\\\ ", 
//						Double.parseDouble(table[r][cols-1].toString()), 
//						units[cols-1]);
//			} catch(NumberFormatException e) {
//				sb.append(table[r][cols-1].toString() + " \\\\ ");
//			}
//			sb.append(table[r][cols-1].toString() + " \\\\ ");
			if(border == Borders.All) {
				sb.append("\\hline");
			}
			sb.append("\n");
		}
		
		sb.append("\\end{tabular}\n");
		

		
		if(isTable) {
			if(caption != null) {
				sb.append("\\caption{" + caption + "}\n");
				if(label != null)
					sb.append("\\label{" + label + "}\n");
			}
			if(centerTable) {
				sb.append("\\end{center}\n");
			}
			sb.append("\\end{table}\n");			
		}
		
		sb.append("%\n");
		
//		System.out.println(sb.toString());
		out.write(sb.toString());		
	}
	
	
	
	private void writeHeader(StringBuilder sb) {
		
		for(int c=0; c<cols-1; c++) {
			sb.append(headers[c] + " & ");
		}
		sb.append(headers[cols-1] + " \\\\ ");
		
		if(border == Borders.All) {
			sb.append("\\hline");
		}
		sb.append("\n");
	}
}
